/*
 * To change this template, choose Tools | Templates
 * and open the template in the editor.
 */

package pt.our.angemc.datatypes;

import java.io.FileNotFoundException;
import java.io.IOException;
import org.antlr.runtime.RecognitionException;
import java.util.ArrayList;
import java.util.Collection;
import java.util.GregorianCalendar;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map.Entry;
import java.util.Set;
import java.util.TreeSet;

/**
 *
 * @author piskeiro
 */
public class Grammar {
    public final String TERMINALS_COUNT             = "terminalsCount";
    public final String NON_TERMINALS_COUNT         = "nonTerminalsCount";
    public final String PRODUCTIONS_COUNT           = "productionsCount";
    public final String UNITARY_PRODUCTIONS_COUNT   = "unitaryProductionsCount";
    public final String RECURSIVE_COUNT             = "recursiveCount";
    public final String RHS_COUNT                   = "rhsCount";
    public final String ALTERNATIVES_COUNT          = "alternativesCount";
    public final String RD_COUNT                    = "rdCount";
    public final String LL1_COUNT                   = "ll1Count";
    public final String SYNATRIBS_COUNT             = "synatrbsCount";
    public final String INHATRIBS_COUNT             = "inhatrbsCount";
    public final String NULLABLE_SYMBOLS            = "nullablesymbs";
    public final String NONREACHABLE_SYMBOLS        = "nonreachsymbs";
    public final String USELESS_SYMBOLS             = "uselesssymbs";

    private ArrayList<Rule> rules;
    private HashMap<String, Object> metrics=null;
    private long time1,time2,timeSpent;

    public Grammar(){
        this.rules = new ArrayList<Rule>();
    }

    public Grammar(ArrayList<Rule> rules) {
        this.rules = rules;
    }

    /*Grammar g must be cloned before calling this constructor*/
    public Grammar(Grammar g){
        this.rules=g.getRules();
        this.metrics=g.getMetrics();
    }

    public ArrayList<Rule> getRules() {
        return rules;
    }

    public void setRules(ArrayList<Rule> rules) {
        this.rules = rules;
    }

    public HashMap<String,Object> getMetrics(){
        return metrics;
    }

    public void setMetrics(HashMap<String,Object> metrics){
        this.metrics=metrics;
    }

    public void setBeginTime(){
        time1=new GregorianCalendar().getTimeInMillis();
    }

    public long setEndTime(){
        time2=new GregorianCalendar().getTimeInMillis();
        return (time2-time1);
    }

    public void setAndPrintEndTime(String message){
        time2=new GregorianCalendar().getTimeInMillis();
        System.out.println("> TIME SPENT "+message+": "+(time2-time1)+" miliseconds...");
    }

    public HashMap<String,Object> calculateMetrics(boolean recalculate){
        if(metrics == null || recalculate){
            metrics = new HashMap<String, Object>();
            metrics.put(TERMINALS_COUNT, getTerminalsCount());
            metrics.put(NON_TERMINALS_COUNT, getNonTerminalsCount());
            metrics.put(PRODUCTIONS_COUNT, getProductionsCount());
            metrics.put(UNITARY_PRODUCTIONS_COUNT, getUnitaryProductionsCount());
            metrics.put(RECURSIVE_COUNT, getRecursiveRulesCount());
            metrics.put(RHS_COUNT, getRHSCount());
            metrics.put(ALTERNATIVES_COUNT, getAlternativesCount());
            metrics.put(RD_COUNT, getRDCount());
            metrics.put(LL1_COUNT, getLL1Count());
            metrics.put(SYNATRIBS_COUNT, getSynAtribsCount());
            metrics.put(INHATRIBS_COUNT, getInhAtribsCount());
            metrics.put(NULLABLE_SYMBOLS, getNullableSymbs());
            metrics.put(NONREACHABLE_SYMBOLS, getUnreachableSymbs());
            metrics.put(USELESS_SYMBOLS, getUselessSymbs());
        }
        return metrics;
    }

    public Set<String> getUselessSymbs()
    {
        Set<String> us = new TreeSet<String>();

        Set<String> unreachs = getUnreachableSymbs();
        us.addAll(unreachs);

        for(Rule r : rules)
        {
            boolean notNonGer = true;

            for(Production p : r.getProductions())
            {
                if(notNonGer)
                {
                    for(Symbol s : p.getSymbols())
                    {
                        if(notNonGer)
                        {
                            notNonGer = checkUseless(s);
                        }
                        else
                        {
                            break;
                        }
                    }
                }
                else
                {
                    break;
                }
            }
            if(notNonGer) us.add(r.getLeftSymbol().getName());
        }

        return us;
    }

    public boolean checkUseless(Symbol s)
    {
        boolean flag = true;

        if(s instanceof SymbolsSequence)
        {
            SymbolsSequence ss = ((SymbolsSequence)s);
            for(Symbol sss : ss.getSymbols())
            {
                if(flag)
                {
                    flag = checkUseless(sss);
                }
                else
                {
                    return false;
                }
            }
        }
        else
        {
            if(!(s instanceof NonTerminal))
            {
                return false;
            }
        }

        return flag;
    }




    public Set<String> getUnreachableSymbs()
    {
        Set<String> leftSides = new TreeSet<String>();

        if(!rules.isEmpty())
        {
            Rule axioma = rules.get(0);

            for(int i = 1 ; i < rules.size() ; i++)
            {
                leftSides.add(rules.get(i).getLeftSymbol().getName());
            }


            for(Production p : axioma.getProductions())
            {
                for(Symbol s : p.getSymbols())
                {

                    if(leftSides.contains(s.getName()))
                    {
                        leftSides.remove(s.getName());
                        checkUnreachable(leftSides,s.getName());
                        
                    }

                }
            }
        }

        return leftSides;
    }

    public void checkUnreachable(Set<String> symbs,String symb)
    {
        for(Rule r : rules)
        {
            if(r.getLeftSymbol().getName().equals(symb))
            {
                for(Production p : r.getProductions())
                {
                    for(Symbol s : p.getSymbols())
                    {
                        if(s instanceof SymbolsSequence)
                        {
                            
                            SymbolsSequence ss = (SymbolsSequence) s;
                            for(Symbol sss : ss.getSymbols())
                            {
                                symbs.remove(sss.getName());
                                checkUnreachable(symbs, sss.getName());
                            }
                        }
                        else
                        {

                        if(symbs.contains(s.getName()))
                        {
                            symbs.remove(s.getName());
                            checkUnreachable(symbs,s.getName());
                            
                        }
                        }
                    }
                }

                break;
            }
        }
    }

    public Set<String> getNullableSymbs()
    {
        Set<String> ns = new HashSet<String>();

        for(Rule r : rules)
        {
            boolean flagNull = true;
            for(Production p : r.getProductions())
            {
                if(flagNull)
                {
                    for(Symbol s : p.getSymbols())
                    {
                        if(!s.getMultiplier().equals(Multiplier.STAR) && !s.getMultiplier().equals(Multiplier.QUESTION))
                        {
                            if(s instanceof NonTerminal)
                            {
                                if(!isNullable(s.getName()))
                                {
                                    flagNull = false;
                                    break;
                                }
                            }
                            else
                            {
                                flagNull = false;
                                break;
                            }
                            
                        }                        
                    }
                }
                else
                {
                    break;
                }
            }
            
            if(flagNull) ns.add(r.getLeftSymbol().getName());
        }

        return ns;
    }

    public boolean isNullable(String symb)
    {
        boolean isNull = true;
        for(Rule r : rules)
        {
            if(r.getLeftSymbol().getName().equals(symb))
            {
                for(Production p : r.getProductions())
                {
                    if(isNull)
                    {
                        for(Symbol s : p.getSymbols())
                        {
                            if(!s.getMultiplier().equals(Multiplier.STAR) && !s.getMultiplier().equals(Multiplier.QUESTION))
                            {
                                if(s instanceof NonTerminal)
                                {
                                    if(!isNullable(s.getName()))
                                    {
                                        return false;
                                    }
                                }
                                else
                                {
                                    return false;
                                }
                            }
                        }
                    }
                    else
                    {
                        break;
                    }
                }
                break;
            }
        }

        return isNull;
    }

    public int getSynAtribsCount()
    {
        int i = 0;

        for(Rule r : rules)
        {
            if(r.getAtribsOUT() != null && !r.getAtribsOUT().isEmpty())
            {
                i += r.getAtribsOUT().size();
            }
        }

        return i;
    }

    public int getInhAtribsCount()
    {
        int i = 0;

        for(Rule r : rules)
        {
            if(r.getAtribsIN() != null && !r.getAtribsIN().isEmpty())
            {
                i += r.getAtribsIN().size();
            }
        }

        return i;
    }

    public Rule getRuleByLeftSymbol(Symbol symbol){
        Rule ruleToReturn=null;
        for(Rule rule : rules){
            if(rule.getLeftSymbol().equals(symbol)){
                ruleToReturn=rule;
                break;
            }
        }
        return ruleToReturn;
    }

    public class Pair{
        private Object first;
        private Object second;

        public Pair(Object first, Object second) {
            this.first = first;
            this.second = second;
        }

        public Object getFirst() {
            return first;
        }

        public Object getSecond() {
            return second;
        }

        public void setFirst(Object first) {
            this.first = first;
        }

        public void setSecond(Object second) {
            this.second = second;
        }


    }

    public Set<String> isEverySymbolDefined(ArrayList<Symbol> symbols,Collection<String> leftSideSymbols){
        Set<String> result = new HashSet<String>();
        for(Symbol symbol : symbols){
            if(symbol instanceof NonTerminal){
                if(!leftSideSymbols.contains(symbol.getName())){
                    //problem
                    result.add(symbol.getName());
                }
            }else if(symbol instanceof Terminal){
                if(((Terminal)symbol).getType() == TerminalType.OTHER){
                    if(!leftSideSymbols.contains(symbol.getName())){
                        //problem
                        result.add(symbol.getName());
                    }
                }
            }else if(symbol instanceof SymbolsSequence){
                result.addAll(isEverySymbolDefined(((SymbolsSequence)symbol).getSymbols(),leftSideSymbols));
            }else if(symbol instanceof AlternativeSymbols){
                result.addAll(isEverySymbolDefined(((AlternativeSymbols)symbol).getSymbols(),leftSideSymbols));
            }
        }
        return result;
    }

    public Set<String> isEverySymbolDefined(){
        Set<String> result = new HashSet<String>();
        Collection<String> leftSideSymbols = getLeftSideSymbols(); //nao terminais + terminais com tipo other
        for(Rule rule : rules){
            for(Production production : rule.getProductions()){
                for(Symbol symbol : production.getSymbols()){
                    if(symbol instanceof NonTerminal){
                        if(!leftSideSymbols.contains(symbol.getName())){
                            //problem
                            result.add(symbol.getName());
                        }
                    }else if(symbol instanceof Terminal){
                        if(((Terminal)symbol).getType() == TerminalType.OTHER){
                            if(!leftSideSymbols.contains(symbol.getName())){
                                //problem
                                result.add(symbol.getName());
                            }
                        }
                    }else if(symbol instanceof SymbolsSequence){
                        result.addAll(isEverySymbolDefined(((SymbolsSequence)symbol).getSymbols(),leftSideSymbols));
                    }else if(symbol instanceof AlternativeSymbols){
                        result.addAll(isEverySymbolDefined(((AlternativeSymbols)symbol).getSymbols(),leftSideSymbols));
                    }
                }
            }
        }
        return result;
    }


    public HashMap<Symbol,Pair> getFinFout(){
        HashMap<Symbol,Pair> map = new HashMap<Symbol,Pair>();
        Collection<Symbol> set;
        for(Rule rule : rules){ //here the fin's are calculated
            if(rule.getLeftSymbol() instanceof NonTerminal){
                set=getRHSSet(rule);
                map.put(rule.getLeftSymbol(), new Pair(set, new ArrayList<Symbol>())); // pair.first=fin  pair.second=fout
            }
        }
        Pair pair=null;
        for(Symbol symbol : map.keySet()){ //here the fout's are calculated
            //System.out.println(symbol.getName());
            pair = map.get(symbol);
            for(Symbol symb : ((Collection<Symbol>)(pair.getFirst()))){
                if(symb instanceof NonTerminal){
                    try
                    {
                    ((Collection<Symbol>)(map.get(symb).getSecond())).add(symbol);
                    }
                    catch(Exception e) { System.out.println(symb.getName());};
                }
            }
        }
        return map;
    }

    private Collection<Rule> getRecursiveRules(ArrayList<Symbol> symbols,Rule ruleWithNonTerminalToSearch,ArrayList<Symbol> visited){
        Collection<Rule> recursiveRules = new ArrayList<Rule>();
        for(Symbol symbol : symbols){
            if(symbol instanceof NonTerminal){
                if(ruleWithNonTerminalToSearch.getLeftSymbol().equals(symbol)){
//                    System.out.println("\tS_OK>"+symbol.toString());
                    recursiveRules.add(ruleWithNonTerminalToSearch);
                    break;
                }else{
//                    System.out.println("\tS>"+symbol.toString());
                    Rule anotherRuleWhereToSearch = getRuleByLeftSymbol(symbol);
                    if(anotherRuleWhereToSearch!=null && !visited.contains(anotherRuleWhereToSearch.getLeftSymbol())){
                        recursiveRules.addAll(getRecursiveRules(anotherRuleWhereToSearch, ruleWithNonTerminalToSearch,visited));
                    }
                }
            }else if(symbol instanceof SymbolsSequence){
                recursiveRules.addAll(getRecursiveRules(((SymbolsSequence)symbol).getSymbols(), ruleWithNonTerminalToSearch,visited));
            }else if(symbol instanceof AlternativeSymbols){
                recursiveRules.addAll(getRecursiveRules(((AlternativeSymbols)symbol).getSymbols(), ruleWithNonTerminalToSearch,visited));
            }
        }
        return recursiveRules;
    }

    private Collection<Rule> getRecursiveRules(Rule anotherRuleWhereToSearch, Rule ruleWithNonTerminalToSearch,ArrayList<Symbol> visited){
        Collection<Rule> recursiveRules = new ArrayList<Rule>();
        visited.add(anotherRuleWhereToSearch.getLeftSymbol());
        for(Production production : anotherRuleWhereToSearch.getProductions()){
            recursiveRules.addAll(getRecursiveRules(production.getSymbols(), ruleWithNonTerminalToSearch,visited));
        }
        return recursiveRules;
    }

    public Collection<Rule> getRecursiveRules(){
        Collection<Rule> recursiveRules = new ArrayList<Rule>();
        for(Rule rule : rules){
            if(rule.getLeftSymbol() instanceof NonTerminal){
                ArrayList<Symbol> visited = new ArrayList<Symbol>();
                visited.add(rule.getLeftSymbol());
//                System.out.println("NON_T>"+rule.getLeftSymbol().toString()+"+++++++++++++++++++++++++++++++++++++++++++++++++++++++++");
                for(Production production : rule.getProductions()){
                    recursiveRules.addAll(getRecursiveRules(production.getSymbols(), rule,visited));
                }
//                System.out.println("NON_T>"+rule.getLeftSymbol().toString()+"---------------------------------------------------------");
            }
        }
        return recursiveRules;
    }

    public int getRecursiveRulesCount(){
        return getRecursiveRules().size();
    }

    public int getLL1Count(){
        int count=0;
        int nonTerminals = (Integer)metrics.get(NON_TERMINALS_COUNT);
        int terminals    = (Integer)metrics.get(TERMINALS_COUNT);
        count=nonTerminals*terminals;
        return count;
    }

    public int getRDCount(){
        int count=0;
        int nonTerminals = (Integer)metrics.get(NON_TERMINALS_COUNT);
        //int terminals    = (Integer)metrics.get(TERMINALS_COUNT);
        count=nonTerminals+1;
        return count;
    }

    public float getAlternativesCount(){
        float res=0.0f;
        try{
            float productions  = (Integer)metrics.get(PRODUCTIONS_COUNT);
            float nonTerminals = (Integer)metrics.get(NON_TERMINALS_COUNT);
            res=productions/nonTerminals;
        }catch(Exception e){
        }
        return res;
    }

    public int getUnitaryProductionsCount(){
        return getUnitaryProductionsSet().size();
    }

//    public HashSet<Rule> getUnitaryProductionsSet(){
//        HashSet<Rule> unitaryProductions = new HashSet<Rule>();
//        for(Rule rule : rules){
//            if(rule.getLeftSymbol() instanceof NonTerminal){
//                if(rule.getProductions().size()==1){
//                    Production production = rule.getProductions().get(0);
//                    int count = (getRHSCollection(production.getSymbols())).size();
//                    if(count==1){
//                        unitaryProductions.add(rule);
//                    }
////                    if( production.getSymbols().size()==1 && ( production.getSymbols().get(0) instanceof NonTerminal || production.getSymbols().get(0) instanceof Terminal) ){
////                        unitaryProductions.add(rule);
////                    }
//                }
//            }
//        }
//        return unitaryProductions;
//    }

    public HashSet<Rule> getUnitaryProductionsSet(){
        HashSet<Rule> unitaryProductions = new HashSet<Rule>();
        for(Rule rule : rules){
            if(rule.getLeftSymbol() instanceof NonTerminal){
                if(rule.getProductions().size()==1){
                    Production production = rule.getProductions().get(0);
                    ArrayList<Symbol> rhsCollection = (ArrayList<Symbol>)getRHSCollection(production.getSymbols());
                    int count = rhsCollection.size();
                    if(count==1 && (rhsCollection.get(0) instanceof Terminal || rhsCollection.get(0) instanceof NonTerminal)){
                        unitaryProductions.add(rule);
                    }
                }
            }
        }
        return unitaryProductions;
    }

    public int getNonTerminalsCount(){
        return getNonTerminalsSet().size();
    }

    public TreeSet<String> getNonTerminalsSet(){
        TreeSet<String> nonTerminals = new TreeSet<String>();
        for(Rule rule : rules){
            if(rule.getLeftSymbol() instanceof NonTerminal){
                nonTerminals.add(rule.getLeftSymbol().getName());
            }
        }
        return nonTerminals;
    }
    
    public Collection<String> getLeftSideSymbols(){
        Collection<String> result = new ArrayList<String>();
        for(Rule rule : rules){
            result.add(rule.getLeftSymbol().getName());
        }
        return result;
    }

    private TreeSet<String> joinSets(TreeSet<String> set1,TreeSet<String> set2){
        set1.addAll(set2);
        return set1;
    }

    private TreeSet<String> getTerminalsSet(ArrayList<Symbol> symbols){
        TreeSet<String> terminals = new TreeSet<String>();
        for(Symbol symbol : symbols){
            if(symbol instanceof Terminal){
                terminals.add(symbol.getName());
            }else if(symbol instanceof SymbolsSequence ){
                terminals=joinSets(terminals, getTerminalsSet(((SymbolsSequence)symbol).getSymbols()));
            }else if( symbol instanceof AlternativeSymbols){
                terminals=joinSets(terminals, getTerminalsSet(((AlternativeSymbols)symbol).getSymbols()));
            }
        }
        return terminals;
    }

    public int getTerminalsCount(){
        return getTerminalsSet().size();
    }

    /*This method doesn't return duplicated elements*/
    public TreeSet<String> getTerminalsSet(){
        TreeSet<String> terminals = new TreeSet<String>();
        for(Rule rule : rules){
            if(rule.getLeftSymbol() instanceof NonTerminal){
                for(Production production : rule.getProductions()){
                    for(Symbol symbol : production.getSymbols()){
                        if(symbol instanceof Terminal){
                            terminals.add(symbol.getName());
                        }else if(symbol instanceof SymbolsSequence ){
                            terminals=joinSets(terminals, getTerminalsSet(((SymbolsSequence)symbol).getSymbols()));
                        }else if(symbol instanceof AlternativeSymbols){
                            terminals=joinSets(terminals, getTerminalsSet(((AlternativeSymbols)symbol).getSymbols()));
                        }
                    }
                }
            }
        }
        return terminals;
    }


    private Collection<Symbol> joinCollections(Collection<Symbol> col1,Collection<Symbol> col2){
        col1.addAll(col2);
        return col1;
    }

    private Collection<Symbol> getRHSCollection(ArrayList<Symbol> symbols){
        Collection<Symbol> collection = new ArrayList<Symbol>();
        for(Symbol symbol : symbols){
            if(symbol instanceof Terminal || symbol instanceof NonTerminal){
                collection.add(symbol);
            }else if(symbol instanceof SymbolsSequence ){
                collection=joinCollections(collection, getRHSCollection(((SymbolsSequence)symbol).getSymbols()));
            }else if(symbol instanceof AlternativeSymbols){
                collection=joinCollections(collection, getRHSCollection(((AlternativeSymbols)symbol).getSymbols()));
            }
        }
        return collection;
    }


    public float getRHSCount(){
        float count=0;
        try{
            float rhsCollection = getRHSCollection().size();
            float productions = (Integer)metrics.get(PRODUCTIONS_COUNT);
            count = rhsCollection / productions;
        }catch(Exception e){
        }
        return count;
    }

    private Collection<Symbol> getRHSCollection(Rule rule){
        Collection<Symbol> collection = new ArrayList<Symbol>();
        for(Production production : rule.getProductions()){
            collection=joinCollections(collection, getRHSCollection(production.getSymbols()));
        }
        return collection;
    }

    public Collection<Symbol> getRHSCollection(){
        Collection<Symbol> collection = new ArrayList<Symbol>();
        for(Rule rule : rules){
            if(rule.getLeftSymbol() instanceof NonTerminal){
                for(Production production : rule.getProductions()){
                    for(Symbol symbol : production.getSymbols()){
                        if(symbol instanceof Terminal || symbol instanceof NonTerminal){
                            collection.add(symbol);
                        }else if(symbol instanceof SymbolsSequence ){
                            collection=joinCollections(collection, getRHSCollection(((SymbolsSequence)symbol).getSymbols()));
                        }else if(symbol instanceof AlternativeSymbols){
                            collection=joinCollections(collection, getRHSCollection(((AlternativeSymbols)symbol).getSymbols()));
                        }
                    }
                }
            }
        }
        return collection;
    }

    private HashSet<Symbol> getRHSSet(ArrayList<Symbol> symbols){
        HashSet<Symbol> set = new HashSet<Symbol>();
        for(Symbol symbol : symbols){
            if(symbol instanceof Terminal || symbol instanceof NonTerminal){
                set.add(symbol);
            }else if(symbol instanceof SymbolsSequence ){
                set.addAll(getRHSSet(((SymbolsSequence)symbol).getSymbols()));
            }else if(symbol instanceof AlternativeSymbols){
                set.addAll(getRHSSet(((AlternativeSymbols)symbol).getSymbols()));
            }
        }
        return set;
    }

    private HashSet<Symbol> getRHSSet(Rule rule){
        HashSet<Symbol> set = new HashSet<Symbol>();
        for(Production production : rule.getProductions()){
            set.addAll(getRHSSet(production.getSymbols()));
        }
        return set;
    }

    public HashSet<Symbol> getRHSSet(){
        HashSet<Symbol> set = new HashSet<Symbol>();
        for(Rule rule : rules){
            if(rule.getLeftSymbol() instanceof NonTerminal){
                for(Production production : rule.getProductions()){
                    for(Symbol symbol : production.getSymbols()){
                        if(symbol instanceof Terminal || symbol instanceof NonTerminal){
                            set.add(symbol);
                        }else if(symbol instanceof SymbolsSequence ){
                            set.addAll(getRHSSet(((SymbolsSequence)symbol).getSymbols()));
                        }else if(symbol instanceof AlternativeSymbols){
                            set.addAll(getRHSSet(((AlternativeSymbols)symbol).getSymbols()));
                        }
                    }
                }
            }
        }
        return set;
    }

    public int getProductionsCount(){
        int count=0;
        for(Rule rule : rules){
            if(rule.getLeftSymbol() instanceof NonTerminal){
                count+=rule.getProductions().size();
            }
        }
        return count;
    }

    public void addRule(Rule rule){
        rules.add(rule);
    }

    public int substitute(String oldS,String newS)
    {
        //1 - OK
        //2 - New Symbol already exists
        //3 - Old Symbol does't exist
        //4 - Cant replace Terminal by NTerminal or viceversa

        int res = 1,exists = 0;

        char o = oldS.charAt(0);
        char n = newS.charAt(0);

        if( ( o >= 'a' && n <= 'Z' ) || ( n >= 'a' && o <= 'Z' ) ) return 4;

        for(Rule r : this.getRules())
        {          
            if(r.getLeftSymbol().getName().equals(newS)) return 2;
            if(r.getLeftSymbol().getName().equals(oldS)) exists = 1;
        }

        if(exists == 0) return 3;

        for(Rule r : this.getRules())
        {
            if(r.getLeftSymbol().getName().equals(oldS))
            {
                r.getLeftSymbol().setName(newS);
            }

            for(Production p : r.getProductions())
            {
                for(Symbol s : p.getSymbols())
                {
                    if(s.getName().equals(oldS)) s.setName(newS);
                }
            }
        }

        return res;
    }
    
    @Override
    public Grammar clone(){
        ArrayList<Rule> newRules = new ArrayList<Rule>();
        for(Rule rule : rules){
            newRules.add(rule.clone());
        }

        HashMap<String,Object> newMetrics = null;
        if(metrics!=null){
            newMetrics = new HashMap<String, Object>();
            for(Entry<String,Object> entry : metrics.entrySet()){
                newMetrics.put(entry.getKey(), entry.getValue());
            }
        }
        Grammar grammar = new Grammar(newRules);
        grammar.setMetrics(newMetrics);
        return grammar;
    }

    @Override
    public String toString(){
        StringBuffer sb = new StringBuffer();
        for(Rule rule : rules){
            sb.append(rule.toString());
        }
        return sb.toString();
    }

    public static void main(String[] args) throws RecognitionException, FileNotFoundException, IOException{
//        Grammar grammar = new Grammar(new ArrayList<Rule>());
//
//        Rule rule = new Rule(new NonTerminal("grammar_sq", Multiplier.NONE,false), new ArrayList<Production>());
//        Production production = new Production(new ArrayList<Symbol>());
//        SymbolsSequence symbolsSequence = new SymbolsSequence("", Multiplier.STAR, new ArrayList<Symbol>(),false);
//        symbolsSequence.addSymbol(new NonTerminal("rule", Multiplier.NONE,false));
//        production.addSymbol(symbolsSequence);
//        rule.addProduction(production);
//        grammar.addRule(rule);
//
//        rule = new Rule(new NonTerminal("rule", Multiplier.NONE,false), new ArrayList<Production>());
//        production = new Production(new ArrayList<Symbol>());
//        NonTerminal nonTerminal = new NonTerminal("identifier", Multiplier.NONE,false);
//        production.addSymbol(nonTerminal);
//        Terminal terminal = new Terminal(":", Multiplier.NONE, TerminalType.CHAR,false);
//        production.addSymbol(terminal);
//        nonTerminal = new NonTerminal("alternatives", Multiplier.NONE,false);
//        production.addSymbol(nonTerminal);
//        terminal = new Terminal(";", Multiplier.NONE, TerminalType.CHAR,false);
//        production.addSymbol(terminal);
//        rule.addProduction(production);
//        grammar.addRule(rule);
//
//        rule = new Rule(new NonTerminal("identifier", Multiplier.NONE,false), new ArrayList<Production>());
//        production = new Production(new ArrayList<Symbol>());
//        terminal = new Terminal("ID", Multiplier.NONE, TerminalType.OTHER,false);
//        production.addSymbol(terminal);
//        rule.addProduction(production);
//        grammar.addRule(rule);
//
//        rule = new Rule(new NonTerminal("alternatives", Multiplier.NONE,false), new ArrayList<Production>());
//        production = new Production(new ArrayList<Symbol>());
//        nonTerminal = new NonTerminal("elements", Multiplier.NONE,false);
//        production.addSymbol(nonTerminal);
//        symbolsSequence = new SymbolsSequence("", Multiplier.STAR, new ArrayList<Symbol>(),false);
//        symbolsSequence.addSymbol(new Terminal("|", Multiplier.NONE, TerminalType.CHAR,false));
//        symbolsSequence.addSymbol(new NonTerminal("elements", Multiplier.NONE,false));
//        production.addSymbol(symbolsSequence);
//        rule.addProduction(production);
//        grammar.addRule(rule);
//
//        rule = new Rule(new NonTerminal("elements", Multiplier.NONE,false), new ArrayList<Production>());
//        production = new Production(new ArrayList<Symbol>());
//        symbolsSequence = new SymbolsSequence("", Multiplier.PLUS, new ArrayList<Symbol>(),false);
//        symbolsSequence.addSymbol(new NonTerminal("element", Multiplier.NONE,false));
//        production.addSymbol(symbolsSequence);
//        rule.addProduction(production);
//        grammar.addRule(rule);
//
//        rule = new Rule(new NonTerminal("element", Multiplier.NONE,false), new ArrayList<Production>());
//        production = new Production(new ArrayList<Symbol>());
//        symbolsSequence = new SymbolsSequence("", Multiplier.QUESTION, new ArrayList<Symbol>(),false);
//        symbolsSequence.addSymbol(new Terminal("~", Multiplier.NONE, TerminalType.CHAR,false));
//        production.addSymbol(symbolsSequence);
//        nonTerminal = new NonTerminal("single", Multiplier.NONE,false);
//        production.addSymbol(nonTerminal);
//        symbolsSequence = new SymbolsSequence("", Multiplier.QUESTION, new ArrayList<Symbol>(),false);
//        symbolsSequence.addSymbol(new NonTerminal("multiplier", Multiplier.NONE,false));
//        production.addSymbol(symbolsSequence);
//        rule.addProduction(production);
//        production = new Production(new ArrayList<Symbol>());
//        symbolsSequence = new SymbolsSequence("", Multiplier.QUESTION, new ArrayList<Symbol>(),false);
//        symbolsSequence.addSymbol(new Terminal("~", Multiplier.NONE, TerminalType.CHAR,false));
//        production.addSymbol(symbolsSequence);
//        terminal = new Terminal("(", Multiplier.NONE, TerminalType.CHAR,false);
//        production.addSymbol(terminal);
//        nonTerminal = new NonTerminal("alternatives", Multiplier.NONE,false);
//        production.addSymbol(nonTerminal);
//        terminal = new Terminal(")", Multiplier.NONE, TerminalType.CHAR,false);
//        production.addSymbol(terminal);
//        symbolsSequence = new SymbolsSequence("", Multiplier.QUESTION, new ArrayList<Symbol>(),false);
//        symbolsSequence.addSymbol(new NonTerminal("multiplier", Multiplier.NONE,false));
//        production.addSymbol(symbolsSequence);
//        rule.addProduction(production);
//        grammar.addRule(rule);
//
//        rule = new Rule(new NonTerminal("multiplier", Multiplier.NONE,false), new ArrayList<Production>());
//        production = new Production(new ArrayList<Symbol>());
//        terminal = new Terminal("*", Multiplier.NONE, TerminalType.CHAR,false);
//        production.addSymbol(terminal);
//        rule.addProduction(production);
//        production = new Production(new ArrayList<Symbol>());
//        terminal = new Terminal("+", Multiplier.NONE, TerminalType.CHAR,false);
//        production.addSymbol(terminal);
//        rule.addProduction(production);
//        production = new Production(new ArrayList<Symbol>());
//        terminal = new Terminal("?", Multiplier.NONE, TerminalType.CHAR,false);
//        production.addSymbol(terminal);
//        rule.addProduction(production);
//        grammar.addRule(rule);
//
//        rule = new Rule(new NonTerminal("single", Multiplier.NONE,false), new ArrayList<Production>());
//        production = new Production(new ArrayList<Symbol>());
//        terminal = new Terminal("STRING", Multiplier.NONE, TerminalType.OTHER,false);
//        production.addSymbol(terminal);
//        symbolsSequence = new SymbolsSequence("", Multiplier.QUESTION, new ArrayList<Symbol>(),false);
//        terminal = new Terminal("..", Multiplier.NONE, TerminalType.CHAR,false);
//        symbolsSequence.addSymbol(terminal);
//        terminal = new Terminal("STRING", Multiplier.NONE, TerminalType.OTHER,false);
//        symbolsSequence.addSymbol(terminal);
//        production.addSymbol(symbolsSequence);
//        rule.addProduction(production);
//        production = new Production(new ArrayList<Symbol>());
//        nonTerminal = new NonTerminal("identifier", Multiplier.NONE,false);
//        production.addSymbol(nonTerminal);
//        rule.addProduction(production);
//        grammar.addRule(rule);
//
//        rule = new Rule(new Terminal("ID", Multiplier.NONE,TerminalType.OTHER,false), new ArrayList<Production>());
//        production = new Production(new ArrayList<Symbol>());
//        AlternativeSymbols alternative = new AlternativeSymbols("", Multiplier.NONE, new ArrayList<Symbol>(),false);
//        terminal = new Terminal("a..z", Multiplier.NONE, TerminalType.SEQUENCE,false);
//        alternative.addSymbol(terminal);
//        terminal = new Terminal("A..Z", Multiplier.NONE, TerminalType.SEQUENCE,false);
//        alternative.addSymbol(terminal);
//        terminal = new Terminal("_", Multiplier.NONE, TerminalType.CHAR,false);
//        alternative.addSymbol(terminal);
//        production.addSymbol(alternative);
//        alternative = new AlternativeSymbols("", Multiplier.STAR, new ArrayList<Symbol>(),false);
//        terminal = new Terminal("a..z", Multiplier.NONE, TerminalType.SEQUENCE,false);
//        alternative.addSymbol(terminal);
//        terminal = new Terminal("A..Z", Multiplier.NONE, TerminalType.SEQUENCE,false);
//        alternative.addSymbol(terminal);
//        terminal = new Terminal("0..9", Multiplier.NONE, TerminalType.SEQUENCE,false);
//        alternative.addSymbol(terminal);
//        terminal = new Terminal("_", Multiplier.NONE, TerminalType.CHAR,false);
//        alternative.addSymbol(terminal);
//        production.addSymbol(alternative);
//        rule.addProduction(production);
//        grammar.addRule(rule);
//
//        rule = new Rule(new Terminal("ID", Multiplier.NONE,TerminalType.OTHER,false), new ArrayList<Production>());
//        production = new Production(new ArrayList<Symbol>());
//        terminal = new Terminal("\"", Multiplier.NONE, TerminalType.CHAR,false);
//        production.addSymbol(terminal);
//        alternative = new AlternativeSymbols("", Multiplier.STAR, new ArrayList<Symbol>(),false);
//        terminal = new Terminal("ESC_SEQ", Multiplier.NONE, TerminalType.OTHER,false);
//        alternative.addSymbol(terminal);
//        AlternativeSymbols alternative2 = new AlternativeSymbols("", Multiplier.NONE, new ArrayList<Symbol>(),true);
//        terminal = new Terminal("\\\\", Multiplier.NONE, TerminalType.CHAR,false);
//        alternative2.addSymbol(terminal);
//        terminal = new Terminal("\"", Multiplier.NONE, TerminalType.CHAR,false);
//        alternative2.addSymbol(terminal);
//        alternative.addSymbol(alternative2);
//        production.addSymbol(alternative);
//        terminal = new Terminal("\"", Multiplier.NONE, TerminalType.CHAR,false);
//        production.addSymbol(terminal);
//        rule.addProduction(production);
//        production = new Production(new ArrayList<Symbol>());
//        terminal = new Terminal("'", Multiplier.NONE, TerminalType.CHAR,false);
//        production.addSymbol(terminal);
//        alternative = new AlternativeSymbols("", Multiplier.STAR, new ArrayList<Symbol>(),false);
//        terminal = new Terminal("ESC_SEQ", Multiplier.NONE, TerminalType.OTHER,false);
//        alternative.addSymbol(terminal);
//        alternative2 = new AlternativeSymbols("", Multiplier.NONE, new ArrayList<Symbol>(),true);
//        terminal = new Terminal("\\\\", Multiplier.NONE, TerminalType.CHAR,false);
//        alternative2.addSymbol(terminal);
//        terminal = new Terminal("'", Multiplier.NONE, TerminalType.CHAR,false);
//        alternative2.addSymbol(terminal);
//        alternative.addSymbol(alternative2);
//        production.addSymbol(alternative);
//        terminal = new Terminal("'", Multiplier.NONE, TerminalType.CHAR,false);
//        production.addSymbol(terminal);
//        rule.addProduction(production);
//        grammar.addRule(rule);
//
//
//        System.out.println(grammar.toString());
//
////        System.out.println("--------------------------------------------------------");
////        System.out.println("Non terminals count: "+grammar.getNonTerminalsCount());
////        System.out.println("--------------------------------------------------------");
////        TreeSet<String> terminals = grammar.getTerminalsSet();
////        System.out.println("Terminals count: "+terminals.size());
////        for(String s : terminals){
////            System.out.println("\t"+s);
////        }
////        System.out.println("--------------------------------------------------------");
////        System.out.println("Productions count: "+grammar.getProductionsCount());
////        System.out.println("--------------------------------------------------------");
////        Collection<String> coll=grammar.getRHSCollection();
////        System.out.println("Right hand side count: "+coll.size());
////        for(String s : coll){
////            System.out.println("\t"+s);
////        }
////        System.out.println("--------------------------------------------------------");
//
//
//        NumberFormat nf = new DecimalFormat("########.##");
//        for(Entry<String,Object> metric : grammar.calculateMetrics(false).entrySet()){
//            if(metric.getValue().getClass().toString().equals("class java.lang.Float")){
//                System.out.println(metric.getKey()+": "+nf.format(metric.getValue()));
//            }else{
//                System.out.println(metric.getKey()+": "+metric.getValue());
//            }
//        }
//        System.out.println("---------------------------------------------------------------------------");
//        HashMap<Symbol,Pair> map = grammar.getFinFout();
//        for(Symbol symbol : map.keySet()){
//            System.out.println("> "+symbol.toString());
//            System.out.println("\t> FIN set");
//            Pair pair = map.get(symbol);
//            for(Symbol symb : ((Collection<Symbol>)(pair.getFirst()))){
//                System.out.println("\t\t "+symb.toString());
//            }
//            System.out.println("\t> FOUT set");
//            for(Symbol symb : ((Collection<Symbol>)(pair.getSecond()))){
//                System.out.println("\t\t "+symb.toString());
//            }
//            System.out.println("\n");
//        }
//        System.out.println("---------------------------------------------------------------------------");
//        RemoveUnitaryProductions grammarWithUnitaryProductionsRemoved = new RemoveUnitaryProductions(grammar.clone());
//        grammarWithUnitaryProductionsRemoved.performModification();
//        System.out.println(grammarWithUnitaryProductionsRemoved.toString());
////        for(Entry<String,Object> metric : grammarWithUnitaryProductionsRemoved.predictModificationMetrics().entrySet()){
//        for(Entry<String,Object> metric : grammarWithUnitaryProductionsRemoved.calculateModificationMetrics().entrySet()){
//            if(metric.getValue().getClass().toString().equals("class java.lang.Float")){
//                System.out.println(metric.getKey()+": "+nf.format(metric.getValue()));
//            }else{
//                System.out.println(metric.getKey()+": "+metric.getValue());
//            }
//        }
//
//        java.lang.Runtime runtime = Runtime.getRuntime();
//        int divideBy=1024;
//        System.out.println("*************************************************");
//        System.out.println("* Memory usage **********************************");
//        System.out.println("*************************************************");
//        System.out.println("* Total : "+(runtime.totalMemory()/divideBy)+" KB");
//        System.out.println("* Free  : "+(runtime.freeMemory()/divideBy)+" KB");
//        System.out.println("* Used  : "+((runtime.totalMemory()-runtime.freeMemory())/divideBy)+" KB");
////        System.out.println("* Max   : "+(runtime.maxMemory()/divideBy)+" KB");
//        System.out.println("*************************************************");
//

    }
}
